/*****************************************************************************
*       ___  ___  ____
*      |   \| _ \/ __/                 Destructable Revival System
*      | \\ |   /\__ \                    by Bannar
*      |___/|_|\_\___/ v3.0.0.0
*
*    Revives dead destructables.
*   
*    Special thanks to Bribe.
*    
*    https://www.hiveworkshop.com/threads/snippet-destructablerevival.233090/
*
******************************************************************************
*
*    Requirements:
*
*       Alloc - choose whatever you like
*          e.g.: by Sevion hiveworkshop.com/threads/snippet-alloc.192348/
*
*       ListT by Bannar
*          hiveworkshop.com/threads/containers-list-t.249011/
*
******************************************************************************
*
*    Configurables:
*
*       struct DestructableRevival:   static boolean enabled
*
*    Functions:
*
*
*****************************************************************************/
library DestructableRevival requires Alloc, ListT

    public function interface DestructableFilter takes destructable whichDest returns boolean
    
    public function interface TimeGeter takes destructable whichDest returns real

    globals
        private Table table = 0
        private timer looper = CreateTimer()
        private trigger trig = CreateTrigger()
        private destructable argDest = null

        private IntegerList ongoing = 0
        
        private DestructableFilter enumFilter = 0
        private TimeGeter revivalDelayGeter = 0
        private TimeGeter remainingGeter = 0
    endglobals

    function SetDestructableRevivalFilter takes DestructableFilter filter returns nothing
        if filter > 0 then
            set enumFilter = filter
        endif
    endfunction

    function SetDestructableRevivalDelayGeter takes TimeGeter geter returns nothing
        if geter > 0 then
            set revivalDelayGeter = geter
        endif
    endfunction

    function SetDestructableRevivalRemainingGeter takes TimeGeter geter returns nothing
        if geter > 0 then
            set remainingGeter = geter
        endif
    endfunction

    function RegisterDestructableRevival takes destructable whichDest returns nothing
        call TriggerRegisterDeathEvent(trig, whichDest)
    endfunction



    private struct PeriodicData extends array
        destructable dest
        real remaining

        implement Alloc

        method destroy takes nothing returns nothing
            call table.remove(GetHandleId(dest))
            set dest = null

            call ongoing.erase(ongoing.find(this))
            if ongoing.empty() then
                call PauseTimer(looper)
            endif

            call deallocate()
        endmethod

        static method create takes destructable d returns thistype
            local thistype this = allocate()

            call ongoing.push(this)
            set this.dest = d
            set table[GetHandleId(d)] = this

            return this
        endmethod
    endstruct

    private function OnCallback takes nothing returns nothing
        local IntegerListItem iter = ongoing.first
        local PeriodicData data
        local boolean flag = true
        local real retReal = 0.0

        loop
            exitwhen iter == 0
            set data = iter.data
            if GetDestructableTypeId(data.dest) == 0 or GetDestructableLife(data.dest) > .405 then
                call data.destroy()
            else
                set data.remaining = data.remaining - 0.031250000

                if data.remaining <= 0 then
                    set argDest = data.dest
                    set retReal = 0
                    
                    if remainingGeter > 0 then
                        set retReal = remainingGeter.evaluate(argDest)
                    endif

                    if retReal > 0 then
                        set data.remaining = retReal
                    else
                        call DestructableRestoreLife(data.dest, GetDestructableMaxLife(data.dest), flag)
                        call data.destroy()
                    endif
                endif
            endif
            set iter = iter.next
        endloop
    endfunction

    private function OnDeath takes nothing returns boolean
        local PeriodicData data

        if not DestructableRevival.enabled then
            return false
        endif

        set argDest = GetTriggerDestructable()

        if enumFilter > 0 then
            if not enumFilter.evaluate(argDest) then
                return false
            endif
        endif

        if not table.has(GetHandleId(argDest)) then
            if ongoing.empty() then
                call TimerStart(looper, 0.031250000, true, function OnCallback)
            endif
            set data = PeriodicData.create(argDest)
        else
            set data = table[GetHandleId(argDest)]
        endif
        
        set data.remaining = 60.0

        if revivalDelayGeter > 0 then
            set data.remaining = revivalDelayGeter.evaluate(argDest)
        endif
        
        return false
    endfunction

    private function RegisterEnumDestructableRevival takes nothing returns nothing
        call RegisterDestructableRevival(GetEnumDestructable())
    endfunction

    private module DestructableRevivalInit
        private static method onInit takes nothing returns nothing
            set ongoing = IntegerList.create()
            set table = Table.create()

            call EnumDestructablesInRect(bj_mapInitialPlayableArea, null, function RegisterEnumDestructableRevival)
            call TriggerAddCondition(trig, Condition(function OnDeath))
        endmethod
    endmodule

    struct DestructableRevival extends array
        static boolean enabled = true

        implement DestructableRevivalInit
    endstruct

endlibrary